iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0

類別 Class 和結構

在 Dart 中所有東西都是一個物件 - 每一個儲存在變數的「值」都是某個類別的物件實例。所有物件都繼承自 Object 類別。這種設計使 Dart 成為一種純粹的物件導向程式語言。

要建立一個類別的物件(實例化),必須使用一個特殊的方法稱為建構子(constructor)。

  • Dart class 可以包含物件成員(方法 method 和欄位 field),還有類別成員(靜態方法和靜態欄位)。
  • 跟 Java 不同,Dart 沒有原始型別;原始型別代表這些型別直接對應原始數據資料,並非物件。在 Dart 中 int float 等都是物件且繼承自 Object 類別。表示它們是可以被覆寫修改行為。
  • Dart 類別不支援建構子重載 overload,重載就是一個類別有多個同名建構子,然後建構子使用不同類型、數量的參數,這讓建立物件的時候可以根據提供不同的參數進行不同的初始化操作。
    雖然 Dart 不支援建構子重載,但支援彈性的參數設計,例如可選參數,位置參數,具名參數。同時也支援具名建構子,當你為建構子命名的時候可以讓類別包含多個建構子,並且語意化。通過這些彈性的設計可以達成重載的目的。
class Point {
  double x, y;
  
  Point(this.x, this.y); // 建構子簡化語法糖
  // 非簡化版本
  // Point(double x, double y) {
  //   this.x = x;
  //   this.y = y;
  // }
  
  Point.origin(): x = 0, y = 0; // 具名建構子
}
void main() {
  var p1 = Point(1, 2);
  var p2 = Point.origin();
}

除了類別之外,Dart 也包含其他物件導向的智慧

  • Interface: 介面用於規範物件該具備什麼方法。從 Dart 3 開始支援顯式介面型別,但依舊可以使用抽象類別
  • Enumerated class: 特殊的類別可定義一系列的常數值
  • Mixin: 一種重複使用程式碼的方式。使用起來像是把一些方法重複的掛載到不同類別。

封裝

封裝是物件導向程式設計的重要原則之一,它讓我們可以隱藏實作細節,只揭露必要的介面。

實務上,Dart 的封裝是在函式庫級別實現的,而不是類別級別。

Dart 沒有明確指定欄位或方法存取範圍的功能,像 Java 有 protectedprivatepublic 等。Dart 會為全部類別中的欄位建立隱式的 getter 和 setter,因此你可以通過這些 getter 和 setter 控制類別的屬性。

class Person {
  String name;
  int _age;
  Person(this.name, this._age);
}

void main() {
  var person = Person('Alice', 20);
  print(person.name); // 可
  print(person._age); // 如果不在同一檔案,不能直接訪問 _age,因為它是私有的。但是同一個庫/檔案是可以的。
}

在 Dart 如果一個類別,類別成員,全域函式或者變數使用 _ 開頭的話就是專屬於該函式庫(私有)。

關於私有屬於檔案層級的範例:

class Person {
  String name;
  int _age;
  
  Person(this.name, this._age);
}

class Employee {
  void age(Person person) {
    print(person._age); // 當在同一個檔案的時候可以存取
  }
}

main() {
  var person = Person('Alice', 20);
  var employee = Employee();
  employee.age(person); // 可以使用 _age
}

繼承和組合

繼承可以讓我們擴展一個類別的定義。

  • Dart 的繼承只能允許單一類別。
  • Dart 支援 mixins 可以擴展類別的功能而不依賴繼承。
  • 類別使用 extends 關鍵字定義繼承。
  • Dart 3.0 之後類別也可以使用更多的修飾子例如 final
final class Point {
  final double x;
  final double y;
  
  Point(this.x, this.y);
}

標記 final 表示該類別不能被繼承。類別中的成員 xy 也被標記為 final,這表示這些成員一旦被初始化後,它們的值就不能被改變。

抽象

繼承外,還有抽象類別用來定義類別的特徵,而不直接實作。

  • 抽象類別定義某個事物該提供什麼,而不須直接實作。
  • 抽象方法是一個方法的簽章 - 即定義了方法和參數但不實作。
  • 抽象類別可以包含具體實作的方法和抽象方法。
  • 任何抽象方法的類別本身也必須是抽象類別,反過來說一般類別的方法都必須實作。
  • 類別繼承了抽象類別的話必須要自己實作規範的方法。
  • Dart 還支援強大的隱式介面的概念,可以讓所有類別也是介面,也就是可以實作 implements 而不是繼承
  • Dart 3 引進了 interface 類別修飾子
class Car {
  void drive() {
    //
  }
}

// 實作類別介面
class ElectricCar implements Car {
  @override
  void drive() {}
}

abstract class Vehicle {
  void drive();
}
class Car implements Vehicle {
  @override
  void drive() {}
}
  
interface IVehicle {
  void drive();
}

class ElectricCar implements IVehicle {
  @override
  void drive() {}
}

extends v.s implements

當你想要保留某個類別的功能進而擴展時可以使用繼承 extends,一個類別只能繼承一個父類別;單一繼承

而實現 implements 介面則是用來強制某個類別遵守某個規範,實現介面不會繼承任何已經實作的程式碼,必須自己實現。

抽象類別和介面的差異:抽象類別可以包含局部實作和局部規範,可共享程式碼。

至於介面只能定義方法簽章。

  • 繼承:只能單一繼承表示 is-a 的關係,通過 extends 實現,子類別會獲得父類別的功能可以直接使用或覆寫
  • 介面:是一種表示 behaves-as 的關係,在 Dart 類別也可以作為介面通過 implements 語法實現。一個類別可以實現多個介面,必須提供介面宣告的全部方法
  • 混入(mixin):使用 with 實現,用於共用方法,不適合單一繼承或介面規範重複一樣的實作。
class Vehicle {
  void drive() {
    print('move');
  }
}

class Car implements Vehicle {
  @override
  void drive() {
    print('move with gas');
  }
}

class ElectricCar extends Car {
  @override
  void drive() {
    print('move silently');
  }
}

mixin AutoDriving {
  void navigate() {
    print('navigating');
  }
}

class Tesla extends ElectricCar with AutoDriving {
  @override
  void drive() {
    super.drive();
    navigate();
  }
}

Mixin

Mixin 是 Dart 中重用類別代碼的一種方式,不需要複雜的繼承層次:

mixin Swimmable {
  void swim() {
    print('Swimming');
  }
}

class Duck extends Animal with Swimmable {}

多型(多態) Polymorphism

多型簡單的說就是物件允許被視為父類別的一種物件實例,可視為一個物件表現的像另一個物件的能力例如 int 也是一種 num。

通過覆寫方法,子類可以改變父類同名方法的行為,也就是在呼叫的時候可視為同一個動作,但是具體實現不一樣。

另外則是要注意:Dart 不支援方法重載,你不能用不同參數定義兩個同名的方法,如果有這種需求嘗試使用可選,位置,具名參數的方式來實現,也就是同一個方法支援各種參數。

會提到不支援方法重載 overload 的重點在於我們可以通過例如可選參數的方式,在維持一樣參數的情況下達成各種需要的覆寫。

小結物件導向三大特性

物件導向的三大特性:封裝、繼承、多型(多態)

  • 封裝:把實作細節包裝隱藏起來,如 Java 的 public private 這些實作,Dart 是檔案層級。
  • 繼承:擴展類別。
  • 多型:一個事物存在多種型態;例如貓是一種動物,狗也是一種動物,如此來看體現了動物的多種型態。具體來說就是父類別的型別宣告可以接受子類別的物件實例,進一步說 Cat 和 Dog 都實作了 move 方法但是細節卻不同,一個變數可以 Animal a = Dog() 如此一來變數都可以執行 a.move() 卻有不同的表現形式。

函式作為物件

Dart 稱為真物件導向語言,在 Dart 中函式也是物件,意味著:

  • 可以把函式賦值給變數,傳參。
  • 可以作為回傳結果。

這被稱為 first-class functions 因為它們可以和其他的型別一樣,並且在 Flutter 中常被用來建立 Widget。

總結

Dart 的物件導向特性提供了強大而靈活的工具來組織和構建你的代碼。

隨著你在 Flutter 開發中的深入,你會發現這些物件導向的概念如何幫助你構建複雜的 UI 和管理應用狀態。如果是第一次從腳本類型語言踏進物件導向的人來說,Dart 的類別結構和語法可能會顯得繁瑣和複雜。確實可能帶來一定的學習曲線。然而,儘管初期可能感到困難,但掌握 OOP 的概念和技巧將可以提升程式的可維護性、可重用性和擴展性。


上一篇
Day 5 Dart 基礎 (下)
下一篇
Day 7 Dart 物件導向 (下)
系列文
Flutter 開發實戰 - 30 天逃離新手村38
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言